package router

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	cedar_session "github.com/tungyao/cedar-session"

	"github.com/gorilla/websocket"
	"github.com/tungyao/spruce"
)

/*
*所有的用户都是由该程序控制
* MSG must include UName
 */
const (
	// to server
	CONNECTED  = 0 // 连接了
	DISCONNECT = 1 // 断开了
	BROADCAST  = 2 // 广播
	SINGLE     = 3 // 私聊      // 可以用于客服这些
	GROUP      = 4 // 群组聊天  // 群组只有 注册用户才能使用
	JOIN       = 5 // 加入群组

	// to client
	CREATE = 6 // 创建群组
	ERROR  = 7 // 返回错误
	NOSEND = 8
)

// 分为三层结构
var (
	allClients *spruce.Hash
	newline    = []byte{'\n'}
	space      = []byte{' '}
	upgrade    = websocket.Upgrader{
		ReadBufferSize:  2048,
		WriteBufferSize: 2048,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	groupClients sync.Map
	hub          *Hub
)

const (
	writeWait      = 10 * time.Second
	pongWait       = 60 * time.Second
	pingPeriod     = (pongWait * 9) / 10
	maxMessageSize = 2048
)

func init() {
	// 用来维护所有用户
	allClients = spruce.CreateHash(40960)
	// 用来存放群组
	// groupClients = spruce.CreateHash(40960)
	hub = newHub()
	go hub.run()
	// TODO 读取数据库中appid到etcd中 以方便后续进行验证
	readAppToEtcd()
	log.Println("read app info success")
}

// TODO its tool func
// 这一层暂时不要动

func ComplementHex(s string, x int) string {
	if len(s) == x {
		return s
	}
	if len(s) < x {
		for i := 0; i < x-len(s); i++ {
			s += "0"
		}
	}
	if len(s) > x {
		return s[:x]
	}
	return s
}
func newHub() *Hub {
	return &Hub{
		broadcast:     make(chan []byte, 5000),
		register:      make(chan *Client, 5000),
		unregister:    make(chan *Client, 5000),
		broadcastRoom: make(chan *Room, 5000),
		singleConn:    make(chan *Single, 5000),
	}
}

// 消息核心 所有的连接都要加入到spruce中 设计容量在4w
func (h *Hub) run() {
	for {
		select {
		// 获得一个新的请求 放入spruce
		case client := <-h.register:
			allClients.Set([]byte(client.uuid), client, 0)
			// 在连接的时候返回用户的未读的消息

			// 返回用户当前的uuid
			msg, _ := MSG{
				UUID: client.uuid,
				Body: "create connect success",
				Type: CREATE,
			}.MarshalJSON()
			client.send <- msg
			allNoSendMsg := GetNoSendMsg(client.uuid)

			for _, v := range allNoSendMsg {
				msg, _ = MSG{
					UUID: client.uuid,
					Body: v.Msg,
					Type: NOSEND,
				}.MarshalJSON()
				client.send <- msg
			}
			// client.hub.broadcast <- []byte(client.uuid)
			// 删除连接
		case client := <-h.unregister:
			// 关闭连接
			client.conn.Close()
			// 关闭通道
			close(client.send)
			// 删除连接
			allClients.Delete([]byte(client.uuid))
			// global mode
		case msg := <-h.broadcast:
			for _, v := range allClients.GetAllWithKey() {
				// write json message
				v.Value.(*Client).send <- msg
			}
			// group mode
			// need group id
		case room := <-h.broadcastRoom:
			if all, ok := groupClients.Load(room.Name); ok {
				for _, v := range all.([]*Client) {
					fmt.Println(v)
					m, _ := MSG{
						UUID:     "",
						Room:     room.Name,
						Count:    room.Count,
						UName:    "",
						Body:     string(room.Message),
						Type:     5,
						BodyType: 1,
					}.MarshalJSON()
					v.send <- m
				}
			}

			// single mode
			// need both uuid
			// need field
			// To   recipient
			// Msg  MSG
		case single := <-h.singleConn:
			fmt.Println(single)
			if client := allClients.Get([]byte(single.To)); client != nil {
				d, err := single.Msg.MarshalJSON()
				if err != nil {
					client.(*Client).send <- WsErr(err)
					continue
				}
				client.(*Client).send <- d
			} else {
				client.(*Client).send <- WsErr("not found user or user is offline")
				// 没法出去的消息 还是存到数据库里面吧
				PutNoSendMsg(single)
			}
		}
	}
}

// 负责读 可以是整个逻辑层
func (c *Client) readPump() {
	defer func() {
		c.hub.unregister <- c
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	err := c.conn.SetReadDeadline(time.Now().Add(pongWait))
	if err != nil {
		log.Fatalln(err)
	}
	for {
		j := new(MSG)
		err := c.conn.ReadJSON(j)
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Fatalf("error: %v", err)
			}
			break // what ?? what fuck !!!!
		}
		// 分发
		c.distribute(j)
	}
}

// 负责写
func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()
	for {
		select {
		case message, ok := <-c.send:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			log.Println("send it!!")
			w.Write(message)
			// n := len(c.send)
			// for i := 0; i < n; i++ {
			// 	w.Write(newline)
			// 	w.Write(<-c.send)
			// }
			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// distribute message type
func (c *Client) distribute(msg *MSG) {
	switch msg.Type {
	case CONNECTED:

	case DISCONNECT:
	case BROADCAST:
		c.hub.broadcast <- []byte(msg.Body)
	case GROUP:
		// need group id  ,server must check it
		c.hub.broadcastRoom <- &Room{
			Name:    msg.Room,
			Count:   0,
			Message: B(msg.Body),
		}
		// 在群组中发消息 需要提供以下的 采用通用的MSG消息
		// 只不过group字段需要填写
	case SINGLE:
		// msg.body
		single := &Single{}
		_ = single.UnmarshalJSON([]byte(msg.Body))
		c.hub.singleConn <- single
	case JOIN:
		// it just a event ，not a touter , it can send msg to all the group
		// you must provide group id
		imgroup := &ImJoinGroup{}
		imgroup.UnmarshalJSON([]byte(msg.Body))
		if imgroup.UUID != "" && imgroup.GroupId != "" {
			// put it to allGroup
			if v, ok := groupClients.Load(imgroup.GroupId); ok {
				p := true
				for _, vv := range v.([]*Client) {
					if vv.uuid == imgroup.UUID {
						vv = c
						p = false
					}
				}
				if p {
					v = append(v.([]*Client), c)
				}
				groupClients.Store(imgroup.GroupId, v)
				c.send <- WsSucc("加入成功", strconv.Itoa(len(v.([]*Client))))
			} else {
				// c.send <- WsErr("join group failed")
				groupClients.Store(imgroup.GroupId, []*Client{c})
				c.send <- WsSucc("加入成功", "")
			}
		} else {
			c.send <- WsErr("mistake uuid or group_id")
		}
	default:
		c.send <- WsErr("error operation")
	}
}
func Wss(w http.ResponseWriter, r *http.Request, s cedar_session.Session) {
	// 在注册时 需要验证appid ,也仅仅需要appid
	// 需要用etcd来进行存储
	// 最好是在初始化的时候 写入etcd
	// TODO 校验用户的appid 如果没有uuid 那么就是临时用户
	if EtcdGetSingle("/app/"+r.URL.Query().Get("appid")) == nil {
		ToErrorJson(w, "appid is error")
		return
	}
	cuuid := createUUID(time.Now().String())
	if guuid := r.URL.Query().Get("uuid"); guuid != "" {
		cuuid = guuid
	}
	conn, err := upgrade.Upgrade(w, r, nil)
	if err != nil {
		ToErrorJson(w, err.Error())
		return
	}
	// uuid := createUUID(conn.RemoteAddr().String())
	// roomid := createRoomId(conn.RemoteAddr().String())
	client := &Client{hub: hub, conn: conn, send: make(chan []byte, 2048), uuid: cuuid}
	client.hub.register <- client
	go client.readPump()
	go client.writePump()
}
